#include <assert.h>
#include <stdlib.h>
#include "ULT.h"
#include <pthread.h>
#include "interrupt.h"
#include "linkedlist.c"

int IDSET = 0;
static int initialized = 0;
static int numThreads = 0;
static int nex = 0;
static int tidsFull = 0;
static ThrdCtlBlk *next;
static int destroy = 0;
static ucontext_t origCon;
static ThrdCtlBlk *runningThread;
static int waitingCount;

struct linkedlist *list;

/*
 * Frees TCB, its context and stack pointer
 */
void freeBlk(ThrdCtlBlk *blk){
  if(!blk) return;
  if(blk->context != NULL){
    if(blk->realContext){
      free(blk->context->uc_stack.ss_sp);
      free(blk->context);
      }
  }
  free(blk);
}

/*
 * Free everything
 */
void freeLibrary(){
  freeBlk(runningThread);
  freeBlk(next);
  freeList(list,1);
}


void copyBlk(ThrdCtlBlk *b1, ThrdCtlBlk *b2){
    b2->tid = b1->tid;
    b2->context = (ucontext_t *)malloc(sizeof(ucontext_t));
    b2->context->uc_stack = b1->context->uc_stack;
    b2->context->uc_mcontext = b2->context->uc_mcontext;
    b2->context->uc_sigmask = b1->context->uc_sigmask;

}

/**
int main() {

}  **/




void initializeThreading()
{
	//Do nothing if we have already initialized ULT
	if (initialized == 1)
	{
		return;
	}
	
        initialized = 1;
	
	//Set number of threads to 1
	numThreads = 1;
	
	//Initialize the queue
	list = list_init();
        runningThread = (ThrdCtlBlk *)malloc(sizeof(ThrdCtlBlk));
        runningThread->context = (ucontext_t *)malloc(sizeof(ucontext_t));
        runningThread->context->uc_stack.ss_size = ULT_MIN_STACK;
        runningThread->context->uc_stack.ss_sp = (char *)malloc(ULT_MIN_STACK);
        getcontext(runningThread->context);
        origCon = *(runningThread->context);
	runningThread->tid = 0;
        runningThread->status = RUNNING;
        runningThread->realContext = 1;
        //append(list,runningThread);
        waitingCount = 0;
	
}

/*
 * Assign a Tid
 */
Tid assignId()
{
 



  //decide if we should implement at 0 or 1
  if (list->size == 0){
    if (runningThread->tid !=0) nex = 0;
    else nex = 1;}

  if (nex == ULT_MAX_THREADS) tidsFull = 1;

  //if queue is now full, simply increment the value to be assigned
  //otherwise, search for an unused id
  if (!tidsFull){
    nex++; 
    return nex-1;
  }

  else {
   nex = 0;
   int i;
   for (i = 0; i< ULT_MAX_THREADS; i++){
     if (!threadForTid(list,nex) || nex != runningThread->tid) return nex;
   }
  }
  return -1;

}

	
void stubFn(void (*fn)(void *), void *arg)
{
	fn(arg);
	ULT_DestroyThread(ULT_SELF);
}

/*
 * creates a thread for function passed in argument
 */
Tid ULT_CreateThread(void (*fn)(void *), void *parg)
{
	initializeThreading();
        interruptsOff();
	if (numThreads >= ULT_MAX_THREADS)
	{
                interruptsOn();
		return ULT_NOMORE;
	}
        interruptsOn();

        //initialize the new thread
	ThrdCtlBlk *newThread = (ThrdCtlBlk *)malloc(sizeof(ThrdCtlBlk));
	newThread->context = (ucontext_t *)malloc(sizeof(ucontext_t));
	
        getcontext(newThread->context);
        newThread->context->uc_stack.ss_sp = (char *)malloc(ULT_MIN_STACK);
        newThread->context->uc_stack.ss_size = ULT_MIN_STACK;
        newThread->realContext = 1;

	
	//makecontext
	makecontext(newThread->context,(void(*)())stubFn, 3, fn, parg);
	
	//assign thread tid to new thread
	newThread->tid = assignId();
        interruptsOff();
	append(list,newThread);
	numThreads +=1;
        interruptsOn();
	return newThread->tid;
	

} 

Tid ULT_Switch(Tid wantTid, int useArgContext, ucontext_t *argContext)
{
  volatile int donethat = 0;
  //initialize
  initializeThreading();

 //first, pop requested TCB off que

  //if self, just set next to running
  if(wantTid == ULT_SELF || wantTid == runningThread->tid){ 
    next = runningThread;
    return runningThread->tid;
   }

  else if (wantTid == ULT_ANY){
    next = pop(list);
    if (next->tid == -2) {
      return ULT_NONE;   
      }
  }
  else {
    next = removeNode(list,wantTid);
    if (next->tid == -1 || next->tid == -2){
      return ULT_INVALID;
      }
    }


  if (runningThread->status == WAITING){
    interruptsOff();
    runningThread = next;; 
    waitingCount++;
    interruptsOn();
    setcontext(runningThread->context);
    }

/**
  if (runningThread->status == DEAD){
     //freeBlk(runningThread);
     interruptsOff();
     runningThread= next;
     interruptsOn();
     setcontext(runningThread->context);
  **/
    

   //not sure about this interrupt changing.  May have to fix if broken.
   interruptsOn();
   if(useArgContext){
     interruptsOff();
     runningThread->context = argContext;
     runningThread->realContext = 0;
     interruptsOn();
     }
   else{
     interruptsOff();
     getcontext(runningThread->context);
     interruptsOn();
   }
   interruptsOff();


  //if not initizialized
  if (!donethat){
    donethat = 1;
  
    //push running Thread
    if (!(wantTid == ULT_SELF || runningThread->tid == wantTid)){
      //if called from destroy(self), destroy thread instead of appending it
      if (!destroy){
         append(list, runningThread);
         }
      if (destroy){
       freeBlk(runningThread);
       destroy=0;
       }
      runningThread = next;
    }
    interruptsOn();
    setcontext(runningThread->context);
   }
interruptsOn();
if (wantTid == -1) return runningThread->tid;
return wantTid;
 
} 

/*
 * Calls switch if wantTid is valid
 */
Tid ULT_Yield(Tid wantTid){
  interruptsOff();
  if (wantTid >= -2 && wantTid < ULT_MAX_THREADS){ 
    interruptsOn();
    //passing in origCon because it won't be used anyway
    return ULT_Switch(wantTid,0,&origCon);
  }
   interruptsOn();
   return ULT_INVALID;
 }


/*
 * Destroys thread with requested tid
 */
Tid ULT_DestroyThread(Tid tid)
{
  initializeThreading();
  ThrdCtlBlk *destroyblk;
  int destid;
  Tid comptid;

  //if self is requested, yield to next thread, and mark self for destruction
  interruptsOff();
  if (tid == ULT_SELF || tid == runningThread->tid){
    destroy = 1;
    comptid = runningThread->tid;
    numThreads--;
    Tid yeld = ULT_Yield(ULT_ANY);
    //if none left, exit program
    if (yeld == ULT_NONE) { 
        freeLibrary();
        interruptsOn();
        exit(0); 
     }
  }

  //if request to destroy any, pop the list and destroy resulting thread
  else if (tid == ULT_ANY){
    destroyblk = pop(list);
    interruptsOn();
    if (destroyblk->tid == -2){
       return ULT_NONE;
       }
    interruptsOff();
    numThreads--;
    interruptsOn();
    destid = destroyblk->tid;
    freeBlk(destroyblk);
    return destid;
  }

  //otherwise, remove requested node
  else {
    interruptsOff();
    destroyblk = removeNode(list,tid);

    destid = destroyblk->tid;
    numThreads--;
    interruptsOn();
    if (destid == -1 || destid == -2){
	 return ULT_INVALID;
	}
    freeBlk(destroyblk);
    return tid;
   }


  return ULT_FAILED;
} 

void printThrdList(){
  printList(list);
}

void printRunThread(){
  printf("running thread is: %d\n", runningThread->tid);
}

int threadCount() {
  return numThreads;
}

ThrdCtlBlk *runThread(){
 return runningThread;
}

void appendRunning(ThrdCtlBlk *blk){
  append(list,blk);
}

void checkLibrary(){
  printf("Checking Library\n");
  printf("\tList is:");
  printList(list);
  printf("\tRunningThread is:");
  printf("%d\n", runningThread->tid);
}

void interruptHelper(ucontext_t *contextVP){
  ULT_Switch(ULT_ANY, 1, contextVP);
}







